using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using StupidSpaceThing;

namespace RacerGame
{
	internal static class Bullets
	{
		private static LinkedList<Bullet> bullets = new LinkedList<Bullet>();
		private static LinkedList<Bullet> bulletsHitTest = new LinkedList<Bullet>();
		private static GraphicsDevice device;
		private static Effect effect;
		private static EffectParameter worldParameter;
		private static EffectParameter viewParameter;
		private static EffectParameter projectionParameter;
		private static VertexDeclaration vertexDeclaration;
		//private static EffectParameter colorParameter;
        //private static VertexPositionColorTexture[] vertices = new VertexPositionColorTexture[4];
		//private static int[] indices;
		//private static Matrix scale = Matrix.CreateScale(new Vector3(0.1f, 1, 0.4f));

		public static void Init(GraphicsDevice _device) {
			device = _device;
			vertexDeclaration = new VertexDeclaration(device, VertexPositionColor.VertexElements);
            /*
			indices = new int[] { 0, 1, 2, 2, 1, 3 };
            Vector2 textureUpperLeft = new Vector2(0.0f, 0.0f);
            Vector2 textureUpperRight = new Vector2(1.0f, 0.0f);
            Vector2 textureLowerLeft = new Vector2(0.0f, 1.0f);
            Vector2 textureLowerRight = new Vector2(1.0f, 1.0f);
            vertices[0] = new VertexPositionColorTexture(new Vector3(1, 0, -1), Color.TransparentBlack, textureUpperLeft);
            vertices[1] = new VertexPositionColorTexture(new Vector3(1, 0, 1), Color.TransparentBlack, textureUpperRight);
            vertices[2] = new VertexPositionColorTexture(new Vector3(-1, 0, -1), Color.TransparentBlack, textureLowerLeft);
            vertices[3] = new VertexPositionColorTexture(new Vector3(-1, 0, 1), Color.TransparentBlack, textureLowerRight);
            */
		}
		public static void Init(Effect _effect, Texture2D texture) {
			effect = _effect;
			worldParameter = _effect.Parameters["World"];
			viewParameter = _effect.Parameters["View"];
			projectionParameter = _effect.Parameters["Projection"];
            effect.Parameters["SpriteTexture"].SetValue(texture);
			//colorParameter = _effect.Parameters["Color"];
		}



		public static Vector3 FirePredict(RealGameTime gameTime, FactionEntity owner, FactionEntity target) {
			if( target.Entity.ColObject == null ) {
				return target.Position;
			}

			float projectileSpeed = Bullet.Speed;
			Vector2 enemyPos = target.Position2D;
			Vector2 shootPos = owner.Position2D;
			Vector2 diff = enemyPos - shootPos;
			float a = -projectileSpeed * projectileSpeed;
			float b = 0.0f;
			float c = 0.0f;
			float target_xSpeed = target.Entity.ColObject.Body.LinearVelocity.X * 0.02f;
			float target_ySpeed = target.Entity.ColObject.Body.LinearVelocity.Y * 0.02f;
			a += target_xSpeed * target_xSpeed;
			a += target_ySpeed * target_ySpeed;
			b += diff.X * target_xSpeed;
			b += diff.Y * target_ySpeed;
			c += diff.X * diff.X;
			c += diff.Y * diff.Y;
			b *= 2.0f;
			float t = (float)(-b - Math.Sqrt(b * b - 4 * a * c)) / (2 * a);
			Vector2 res = new Vector2(enemyPos.X + target_xSpeed * t, enemyPos.Y + target_ySpeed * t);

			float angle = (float)Math.Atan2(res.Y - shootPos.Y, res.X - shootPos.X);

			float bullet_xSpeed = (float)Math.Cos(angle) * projectileSpeed;
			float bullet_ySpeed = (float)Math.Sin(angle) * projectileSpeed;
			//Bullets.Fire(new Bullet(owner.Faction.Color, owner, new Vector3(bullet_xSpeed, 0, bullet_ySpeed), yModifier, gameTime, true));
			return new Vector3(bullet_xSpeed, 0, bullet_ySpeed);
		}

		public static void Fire(Color color, FactionEntity owner, Vector3 direction, float yModifier, RealGameTime gameTime) {
			Fire(new Bullet(color, owner, direction, yModifier, gameTime, true));
		}
		public static void Fire(Color color, FactionEntity owner, FactionEntity target, float yModifier, RealGameTime gameTime) {
			Fire(new Bullet(color, owner, target.Position - owner.Position, yModifier, gameTime, true));
		}
		public static void Fire(Bullet bullet){
			bullets.AddLast(bullet);
			if( bullet.HitTest ) {
				bulletsHitTest.AddLast(bullet);
			}
		}

		public static void Remove(Bullet bullet) {
			bullets.Remove(bullet);
			if( bullet.HitTest ) {
				bulletsHitTest.Remove(bullet);
			}
		}

		public static void HitTest(LinkedList<FactionEntity> entities) {
			foreach( Bullet bullet in bulletsHitTest ){				
				if( bullet.Intensity == 0 ) continue;
				foreach( FactionEntity fe in entities ) {
					if( fe == bullet.Owner ) continue;
					if( fe.Entity.Dead ) continue;
					ColObject colObject = fe.Entity.ColObject;
					if( colObject != null && colObject.Within(bullet.Position2D) ) {
						if( fe is Unit ) {
							(fe as Unit).ApplyForce(bullet.Direction2D * 200, true);
						}
						bullet.Die();
						bullet.Intensity = 0;
						if( fe.Faction != bullet.Owner.Faction ) { // no friendly fire
							fe.Entity.ApplyDamage(15, bullet.Owner);
							bullet.Owner.Target = fe;
						}
					}
				}
			}
		}

		public static void Update(RealGameTime gameTime) {
			LinkedListNode<Bullet> curBullet = bullets.First;
			while( curBullet != null ){
				if( gameTime.TotalRealGameTime.TotalMilliseconds - Bullet.LifeSpan > curBullet.Value.Fired ) {
					curBullet.Value.Die();
				}
				if( curBullet.Value.Intensity == 0 ) {
					LinkedListNode<Bullet> nextBullet = curBullet.Next;
					Remove(curBullet.Value);					
					curBullet = nextBullet;
				} else {					
					curBullet.Value.Update(gameTime);
					curBullet = curBullet.Next;
				}
			}			
		}

		public static void Draw(Camera camera) {
            if (bullets.Count == 0)
                return;
			viewParameter.SetValue(camera.View);
			projectionParameter.SetValue(camera.Projection);
            worldParameter.SetValue(Matrix.Identity);
			device.VertexDeclaration = vertexDeclaration;
            device.RenderState.PointSpriteEnable = true;
            device.RenderState.PointSize = 8.0f;
            device.RenderState.AlphaBlendEnable = true;
            device.RenderState.SourceBlend = Blend.SourceAlpha;
            device.RenderState.DestinationBlend = Blend.One;
            device.RenderState.DepthBufferWriteEnable = false;

            VertexPositionColor[] spriteArray = new VertexPositionColor[5 * bullets.Count];
   
            int i = 0;
			foreach( Bullet bullet in bullets ) {
                Vector4 tempVector = bullet.Color.ToVector4();
                tempVector.W = bullet.Intensity;
                Color tempColor = new Color(tempVector);
                spriteArray[i + 0].Position = bullet.Position + 0.50f * bullet.Direction;
                spriteArray[i + 0].Color = tempColor;
                spriteArray[i + 1].Position = bullet.Position - 0.50f * bullet.Direction;
                spriteArray[i + 1].Color = tempColor;
                spriteArray[i + 2].Position = bullet.Position;
                spriteArray[i + 2].Color = tempColor;
                /*spriteArray[i + 3].Position = bullet.Position - 0.25f * bullet.Direction;
                spriteArray[i + 3].Color = tempColor;
                spriteArray[i + 4].Position = bullet.Position + 0.25f * bullet.Direction;
                spriteArray[i + 4].Color = tempColor;*/
                i += 3;
                
			}

            effect.Begin();
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();
                device.DrawUserPrimitives<VertexPositionColor>(PrimitiveType.PointList, spriteArray, 0, spriteArray.Length);
                pass.End();
            }
            effect.End();

            device.RenderState.PointSpriteEnable = false;
            device.RenderState.DepthBufferWriteEnable = true;
            device.RenderState.SourceBlend = Blend.SourceAlpha;
            device.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
		}  
	}

	internal class Bullet
	{
		public readonly Color Color;
		public readonly FactionEntity Owner;
		public readonly Vector3 Direction;
		public Vector2 Direction2D { get { return new Vector2(Direction.X, Direction.Z); } }
		private Vector3 position;
		public Vector3 Position { get { return position; } }
		public Vector2 Position2D { get { return new Vector2(position.X, position.Z); } }
		public readonly double Fired;
		public readonly Matrix RotationMatrix;
		private bool dead = false;
		public bool Dead { get { return dead; } }
		public const float Speed = 1;
		public const int LifeSpan = 400;
		public float Intensity = 1;
		public readonly bool HitTest;
		private Vector3 linearVelocity = Vector3.Zero;

		public Bullet(Color color, FactionEntity owner, Vector3 direction, float yModifier, RealGameTime gameTime, bool hitTest) : this(color, owner, direction, new Vector3(0, yModifier, 0), gameTime, hitTest) { }

		public Bullet(Color color, FactionEntity owner, Vector3 direction, Vector3 positionModifier, RealGameTime gameTime, bool hitTest) {
			if( owner.Collision ) {
				this.linearVelocity = new Vector3(owner.Entity.ColObject.LinearVelocity.X, 0, owner.Entity.ColObject.LinearVelocity.Z) * 0.016f;
			}
			this.Color = color;
			this.Owner = owner;
			this.Direction = direction;
			this.Direction.Normalize();
			this.Direction += Jitter();
			this.Direction.Normalize();
			this.RotationMatrix = Matrix.CreateRotationY((float)Math.Atan2(Direction.X, Direction.Z));
			this.position = owner.Position + positionModifier;
			this.Fired = gameTime.TotalRealGameTime.TotalMilliseconds;
			this.HitTest = hitTest;
		}

		static Bullet() {
			halfJitter = jitter / 2.0f;
		}

		private const float jitter = 0.1f;
		private static float halfJitter;
		private Vector3 Jitter() {
			return new Vector3((float)RacerGame.Random.NextDouble() * jitter - halfJitter, (float)RacerGame.Random.NextDouble() * jitter - halfJitter, (float)RacerGame.Random.NextDouble() * jitter - halfJitter);
		}

		public void Update(RealGameTime gameTime) {
			position += Direction * Speed + linearVelocity;
			if( Dead ) {
				Intensity -= 0.05f;
				if( Intensity < 0 ) {
					Intensity = 0;
				}
			}
		}

		public void Die() {
			dead = true;
		}
	}
}
